#include "stdafx.h"
#include "adodb_impl.h"
#include <sstream>
#include <strsafe.h>
#include <time.h>

#define MAX_PARAMETER_COUNT	2100

ADODB::Impl::Impl() :
		com_init_(false), connection_(NULL), command_(NULL),
		command_type_(adCmdUnspecified), parameter_(NULL),
		recordset_(NULL), parameters_(NULL), fields_(NULL),
		field_(NULL)
{
	output_parameters_.reserve(MAX_PARAMETER_COUNT);
	ZeroMemory(&timestamp_, sizeof(timestamp_));
}

ADODB::Impl::~Impl()
{
	if (com_init_)
	{
		CoUninitialize();
	}

	output_parameters_.clear();
}

bool ADODB::Impl::Connect(const wchar_t* ip, const wchar_t* db, const wchar_t* user, const wchar_t* password, unsigned long timeout)
{
	std::wostringstream connection_stream;
	connection_stream << "Provider=SQLOLEDB; Data Source = " << ip << "; Initial Catalog = " << db << "; User ID = " << user << "; Password = " << password;
	std::wstring connection_string(connection_stream.str());
	return Connect(connection_string.c_str(), timeout);
}

bool ADODB::Impl::Connect(const wchar_t* connection_string, unsigned long timeout)
{
	HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (FAILED(hr))
	{
		return false;
	}
	com_init_ = true;

	hr = CoCreateInstance(__uuidof(Connection), NULL, CLSCTX_INPROC, __uuidof(_Connection), reinterpret_cast<void**>(&connection_));
	if (FAILED(hr))
	{
		return false;
	}

	if (timeout)
	{
		hr = connection_->put_ConnectionTimeout(timeout);
		if (FAILED(hr))
		{
			return false;
		}
	}

	hr = connection_->put_ConnectionString(_bstr_t(connection_string));
	if (FAILED(hr))
	{
		return false;
	}

	hr = connection_->put_CursorLocation(adUseClient);
	if (FAILED(hr))
	{
		return false;
	}

	hr = connection_->Open(_bstr_t(""), _bstr_t(""), _bstr_t(""), NULL);
	if (FAILED(hr))
	{
		return false;
	}

	return true;
}

bool ADODB::Impl::Disconnect()
{
	ReleaseCommand();

	if (connection_ != NULL)
	{
		HRESULT hr = connection_->Close();
		if (FAILED(hr))
		{
			return false;
		}

		connection_->Release();
		connection_ = NULL;
	}

	return true;
}

bool ADODB::Impl::SetCommand(CommandType type, const wchar_t* sql)
{
	ReleaseCommand();

	HRESULT hr = CoCreateInstance(__uuidof(Command), NULL, CLSCTX_INPROC, __uuidof(_Command), reinterpret_cast<void**>(&command_));
	if (FAILED(hr))
	{
		return false;
	}

	hr = command_->putref_ActiveConnection(connection_);
	if (FAILED(hr))
	{
		return false;
	}

	hr = CoCreateInstance(__uuidof(Recordset), NULL, CLSCTX_INPROC, __uuidof(_Recordset), reinterpret_cast<void**>(&recordset_));
	if (FAILED(hr))
	{
		return false;
	}

	hr = recordset_->putref_Source(command_);
	if (FAILED(hr))
	{
		return false;
	}

	hr = command_->put_CommandText(_bstr_t(sql));
	if (FAILED(hr))
	{
		return false;
	}

	switch (type)
	{
	case CommandText:
		command_type_ = adCmdText;
		break;
	case CommandSP:
		command_type_ = adCmdStoredProc;
		break;
	}

	return true;
}

bool ADODB::Impl::SetParameter(_bstr_t column, ParameterDirection direction, DataType type, void* buffer, std::size_t buffer_size)
{
	if (command_ == NULL)
	{
		return false;
	}

	if (parameters_ != NULL)
	{
		return false;
	}

	if (parameter_ != NULL)
	{
		return false;
	}

	DataTypeEnum adtype = adEmpty;

	_variant_t variant(NULL);

	switch (type)
	{
	case DataTinyInt:
		adtype = adTinyInt;
		if (buffer != NULL)
		{
			variant = *static_cast<char*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedTinyInt:
		adtype = adUnsignedTinyInt;
		if (buffer != NULL)
		{
			variant = *static_cast<unsigned char*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataSmallInt:
		adtype = adSmallInt;
		if (buffer != NULL)
		{
			variant = *static_cast<short*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedSmallInt:
		adtype = adUnsignedSmallInt;
		if (buffer != NULL)
		{
			variant = *static_cast<unsigned short*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataInt:
		adtype = adInteger;
		if (buffer != NULL)
		{
			variant = *static_cast<int*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedInt:
		adtype = adInteger;
		if (buffer != NULL)
		{
			variant = *static_cast<unsigned int*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataBigInt:
		adtype = adBigInt;
		if (buffer != NULL)
		{
			variant = *static_cast<__int64*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedBigInt:
		adtype = adUnsignedBigInt;
		if (buffer != NULL)
		{
			variant = *static_cast<unsigned __int64*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataFloat:
		adtype = adSingle;
		if (buffer != NULL)
		{
			variant = *static_cast<float*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataDouble:
		adtype = adDouble;
		if (buffer != NULL)
		{
			variant = *static_cast<double*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataChar:
		adtype = adChar;
		variant = static_cast<const char*>(buffer);
		if (buffer != NULL)
		{
			variant = static_cast<const char*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
			buffer_size = sizeof(char);
		}
		break;
	case DataVarChar:
		adtype = adVarChar;
		if (buffer != NULL)
		{
			variant = static_cast<const char*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
			buffer_size = sizeof(char);
		}
		break;
	case DataWChar:
		adtype = adWChar;
		if (buffer != NULL)
		{
			variant = static_cast<const wchar_t*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
			buffer_size = sizeof(wchar_t);
		}
		break;
	case DataVarWChar:
		adtype = adVarWChar;
		if (buffer != NULL)
		{
			variant = static_cast<const wchar_t*>(buffer);
		}
		else
		{
			variant.ChangeType(VT_NULL);
			buffer_size = sizeof(wchar_t);
		}
		break;
	case DataSmallDateTime:
	case DataDateTime:
		{
			adtype = adDBTimeStamp;
			if (buffer != NULL)
			{
				variant = *static_cast<DATE*>(buffer);
			}
			else
			{
				variant.ChangeType(VT_NULL);
				buffer_size = sizeof(DATE);
			}
			break;
		}
	case DataTimeStamp:
		{
			adtype = adBinary;
			SAFEARRAYBOUND bound[1];
			bound[0].cElements = 8;
			bound[0].lLbound = 0;
			variant.parray = SafeArrayCreate(VT_UI1, 1, bound);
		
			void* data = 0;
			HRESULT hr = SafeArrayAccessData(variant.parray, &data);
			if (FAILED(hr))
			{
				return false;
			}

			memcpy(data, buffer, 8);

			hr = SafeArrayAccessData(variant.parray, &data);
			if (FAILED(hr))
			{
				return false;
			}

			variant.vt = VT_ARRAY | VT_UI1;
			
			SafeArrayDestroy(variant.parray);

			break;
		}
	default:
		return false;
	}

	HRESULT hr = command_->CreateParameter(column, adtype, static_cast<ParameterDirectionEnum>(direction + 1), buffer_size, variant, &parameter_);
	if (FAILED(hr))
	{
		return false;
	}

	hr = command_->get_Parameters(&parameters_);
	if (FAILED(hr))
	{
		return false;
	}

	parameters_->Release();

	hr = parameters_->Append(parameter_);
	if (FAILED(hr))
	{
		return false;
	}

	if (direction & ParameterOutput)
	{
		output_parameters_.push_back(new OutputParameter(column, type, buffer, buffer_size));
	}

	parameter_->Release();
	parameters_ = NULL;
	parameter_ = NULL;

	return true;
}

bool ADODB::Impl::ExecuteCommand(HRESULT& hr, unsigned long timeout)
{
	if (command_ == NULL)
	{
		return false;
	}

	if (parameters_ != NULL)
	{
		return false;
	}

	if (parameter_ != NULL)
	{
		return false;
	}

	if (timeout > 0)
	{
		hr = command_->put_CommandTimeout(timeout);
		if (HRESULT(hr))
		{
			return false;
		}
	}

	hr = recordset_->Open(vtMissing, vtMissing, adOpenForwardOnly, adLockReadOnly, command_type_);
	if (FAILED(hr))
	{
		return false;
	}
	
	if (!output_parameters_.empty())
	{
		OutputParameter* output_parameter = NULL;
		OUTPUT_PARAMETERS::iterator begin = output_parameters_.begin();
		OUTPUT_PARAMETERS::iterator end = output_parameters_.end();

		for (; begin != end; ++begin)
		{
			output_parameter = *begin;

			if (!GetParameterData(output_parameter->column(), output_parameter->type(), output_parameter->buffer(), output_parameter->buffer_size(), NULL))
			{
				return false;
			}

			delete output_parameter;
		}

		output_parameters_.clear();
	}
	
	return true;
}

bool ADODB::Impl::GetRowCount(unsigned long& count)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	for (count = 0; SUCCEEDED(recordset_->MoveNext()); ++count);

	if (count > 0)
	{
		HRESULT hr = recordset_->MoveFirst();
		if (FAILED(hr))
		{
			return false;
		}
	}

	return true;
}

bool ADODB::Impl::GetRowSize(std::size_t& size)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	if (fields_ != NULL)
	{
		return false;
	}

	short column_count = 0;

	if (!GetColumnCount(column_count))
	{
		return false;
	}

	long actual_size = 0;

	for (short count = 0; count < column_count; ++count)
	{
		HRESULT hr = recordset_->get_Fields(&fields_);
		if (FAILED(hr))	{
			return false;
		}
		fields_->Release();

		_variant_t column_variant(static_cast<short>(count));

		hr = fields_->get_Item(column_variant, &field_);
		if (FAILED(hr))	{
			return false;
		}
		field_->Release();

		hr = field_->get_ActualSize(&actual_size);
		if (FAILED(hr))	{
			return false;
		}
		fields_ = NULL;
		field_ = NULL;

		size += actual_size;
	}

	return true;
}

bool ADODB::Impl::GetColumnCount(short& count)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	if (fields_ != NULL)
	{
		return false;
	}

	HRESULT hr = recordset_->get_Fields(&fields_);
	if (FAILED(hr))
	{
		return false;
	}
	fields_->Release();

	long temp_count = 0;
	hr = fields_->get_Count(&temp_count);
	if (FAILED(hr))
	{
		return false;
	}
	fields_ = NULL;
	fields_ = NULL;

	count = static_cast<short>(temp_count);

	return true;
}

bool ADODB::Impl::GetColumnType(_variant_t column, DataType& type)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	if (fields_ != NULL)
	{
		return false;
	}

	HRESULT hr = recordset_->get_Fields(&fields_);
	if (FAILED(hr))
	{
		return false;
	}
	fields_->Release();

	hr = fields_->get_Item(column, &field_);
	if (FAILED(hr))
	{
		return false;
	}
	field_->Release();

	DataTypeEnum adtype;
	hr = field_->get_Type(&adtype);
	if (FAILED(hr))
	{
		return false;
	}
	fields_ = NULL;
	field_ = NULL;

	switch (adtype)
	{
	case adTinyInt:
		type = DataTinyInt;
		break;
	case adUnsignedTinyInt:
		type = DataUnsignedTinyInt;
		break;
	case adSmallInt:
		type = DataSmallInt;
		break;
	case adUnsignedSmallInt:
		type = DataUnsignedSmallInt;
		break;
	case adInteger:
		type = DataInt;
		break;
	case adUnsignedInt:
		type = DataUnsignedInt;
		break;
	case adBigInt:
		type = DataBigInt;
		break;
	case adUnsignedBigInt:
		type = DataUnsignedBigInt;
		break;
	case adSingle:
		type = DataFloat;
		break;
	case adDouble:
		type = DataDouble;
		break;
	case adChar:
		type = DataChar;
		break;
	case adVarChar:
		type = DataVarChar;
		break;
	case adWChar:
		type = DataWChar;
		break;
	case adVarWChar:
		type = DataVarWChar;
		break;
	case adDBTimeStamp:
		type = DataTimeStamp;
		break;
	case adBinary:
		type = DataBinary;
		break;
	}
	
	return true;
}

bool ADODB::Impl::GetColumnActualSize(_variant_t column, std::size_t& size)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	if (fields_ != NULL)
	{
		return false;
	}

	HRESULT hr = recordset_->get_Fields(&fields_);
	if (FAILED(hr))
	{
		return false;
	}
	fields_->Release();

	hr = fields_->get_Item(column, &field_);
	if (FAILED(hr))
	{
		return false;
	}
	field_->Release();

	long temp_size = 0;
	hr = field_->get_ActualSize(&temp_size);
	if (FAILED(hr))
	{
		return false;
	}
	fields_ = NULL;
	field_ = NULL;

	size = temp_size;

	return true;
}

bool ADODB::Impl::GetColumnDefinedSize(_variant_t column, std::size_t& size)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	if (fields_ != NULL)
	{
		return false;
	}

	HRESULT hr = recordset_->get_Fields(&fields_);
	if (FAILED(hr))
	{
		return false;
	}
	fields_->Release();

	hr = fields_->get_Item(column, &field_);
	if (FAILED(hr))
	{
		return false;
	}
	field_->Release();

	long temp_size = 0;
	hr = field_->get_DefinedSize(&temp_size);
	if (FAILED(hr))
	{
		return false;
	}
	fields_ = NULL;
	field_ = NULL;

	size = temp_size;

	return true;
}

bool ADODB::Impl::GetColumnData(_variant_t column, DataType type, void* buffer, std::size_t buffer_size, int* actual_size)
{
	if (recordset_ == NULL)
	{
		return false;
	}

	if (fields_ != NULL)
	{
		return false;
	}

	HRESULT hr = recordset_->get_Fields(&fields_);
	if (FAILED(hr))
	{
		return false;
	}
	fields_->Release();

	hr = fields_->get_Item(column, &field_);
	if (FAILED(hr))
	{
		return false;
	}
	field_->Release();

	_variant_t variant(vtMissing);
	hr = field_->get_Value(&variant);
	if (FAILED(hr))
	{
		return false;
	}
	fields_ = NULL;
	field_ = NULL;

	if (!GetVariantData(variant, buffer, buffer_size,actual_size))
	{
		return false;
	}

	return true;
}

bool ADODB::Impl::IsRowEmpty()
{
	if (recordset_ == NULL)
	{
		return false;
	}

	VARIANT_BOOL eof = FALSE;
	HRESULT hr = recordset_->get_EOF(&eof);
	if (SUCCEEDED(hr) && !eof)
	{
		return false;
	}

	return true;
}

bool ADODB::Impl::MoveNextRow()
{
	if (recordset_ == NULL)
	{
		return false;
	}

	HRESULT hr = recordset_->MoveNext();
	if (FAILED(hr))
	{
		return false;
	}

	VARIANT_BOOL eof = FALSE;
	hr = recordset_->get_EOF(&eof);
	if (FAILED(hr))
	{
		return false;
	}

	if (eof)
	{
		return false;
	}

	return true;
}

void ADODB::Impl::ReleaseCommand()
{
	if (command_ != NULL)
	{
		if (recordset_ != NULL)
		{
			if (fields_ != NULL)
			{
				fields_->Release();
				fields_ = NULL;
			}

			if (field_ != NULL)
			{
				field_->Release();
				field_ = NULL;
			}

			recordset_->Release();
			recordset_ = NULL;
		}

		if (parameters_ != NULL)
		{
			parameters_->Release();
			parameters_ = NULL;
		}

		if (parameter_ != NULL)
		{
			parameter_->Release();
			parameter_ = NULL;
		}

		command_->Release();
		command_ = NULL;

		output_parameters_.clear();
	}
}

bool ADODB::Impl::GetParameterData(_variant_t column, DataType type, void* buffer, std::size_t buffer_size, int* actual_size)
{
	if (parameters_ != NULL)
	{
		return false;
	}

	if (parameter_ != NULL)
	{
		return false;
	}

	HRESULT hr = command_->get_Parameters(&parameters_);
	if (FAILED(hr))
	{
		return false;
	}
	parameters_->Release();

	hr = parameters_->get_Item(column, &parameter_);
	if (FAILED(hr))
	{
		return false;
	}
	parameter_->Release();

	_variant_t variant(vtMissing);
	hr = parameter_->get_Value(&variant);
	if (FAILED(hr))
	{
		return false;
	}
	parameters_ = NULL;
	parameter_ = NULL;

	if (!GetVariantData(variant, buffer, buffer_size, actual_size))
	{
		return false;
	}

	return true;
}

bool ADODB::Impl::GetVariantData(_variant_t& variant, void* buffer, std::size_t buffer_size, int* actual_size)
{
	if (variant.vt == VT_NULL)
	{
		if (actual_size != NULL)
		{
			*actual_size = NULL_DATA;
		}
	}
	else
	{
		void* data = NULL;
		std::size_t size = 0;

		switch (variant.vt)
		{
		case VT_I1:
			data = &variant.cVal;
			size = sizeof(variant.cVal);
			break;
		case VT_UI1:
			data = &variant.bVal;
			size = sizeof(variant.bVal);
			break;
		case VT_I2:
			data = &variant.iVal;
			size = sizeof(variant.iVal);
			break;
		case VT_UI2:
			data = &variant.uiVal;
			size = sizeof(variant.uiVal);
			break;
		case VT_I4:
			data = &variant.intVal;
			size = sizeof(variant.intVal);
			break;
		case VT_UI4:
			data = &variant.uintVal;
			size = sizeof(variant.uintVal);
			break;
		case VT_I8:
			data = &variant.llVal;
			size = sizeof(variant.llVal);
			break;
		case VT_UI8:
			data = &variant.ullVal;
			size = sizeof(variant.ullVal);
			break;
		case VT_R4:
			data = &variant.fltVal;
			size = sizeof(variant.fltVal);
			break;
		case VT_R8:
			data = &variant.dblVal;
			size = sizeof(variant.dblVal);
			break;
		case VT_BSTR:
			{
				std::size_t length = 0;
				HRESULT hr = StringCchLengthW(variant.bstrVal, buffer_size, &length);
				if (FAILED(hr))
				{
					return false;
				}

				data = variant.bstrVal;
				size = length * sizeof(wchar_t);
				break;
			}
		case VT_DATE:
			SYSTEMTIME systime;
			VariantTimeToSystemTime(variant.date, &systime);
			timestamp_.year = systime.wYear;
			timestamp_.month = systime.wMonth;
			timestamp_.day = systime.wDay;
			timestamp_.hour = systime.wHour;
			timestamp_.minute = systime.wMinute;
			timestamp_.second = systime.wSecond;
			timestamp_.fraction = systime.wMilliseconds * 1000000; 
			data = &timestamp_;
			size = sizeof(TIMESTAMP_STRUCT);
			break;
		case VT_ARRAY | VT_UI1:
			{
				HRESULT hr = SafeArrayAccessData(variant.parray, &data);
				if (FAILED(hr))
				{
					return false;
				}

				size = variant.parray->rgsabound->cElements;

				__int64 timestamp = 0;	

				for (unsigned long offset = 0; --size; ++offset)
				{
					reinterpret_cast<BYTE*>(&timestamp)[offset] = reinterpret_cast<BYTE*>(data)[size];
				}

				hr = SafeArrayUnaccessData(variant.parray);
				if (FAILED(hr))
				{
					return false;
				}

				tm time;
				errno_t error = localtime_s(&time, &timestamp);
				if (error != NO_ERROR)
				{
					return false;
				}
				
				timestamp_.year = static_cast<SQLSMALLINT>(time.tm_year + 1900);
				timestamp_.month = static_cast<SQLSMALLINT>(time.tm_mon + 1);
				timestamp_.day = static_cast<SQLSMALLINT>(time.tm_mday);
				timestamp_.hour = static_cast<SQLSMALLINT>(time.tm_hour);
				timestamp_.minute = static_cast<SQLSMALLINT>(time.tm_min);
				timestamp_.second = static_cast<SQLSMALLINT>(time.tm_sec);
				timestamp_.fraction = 0;

				data = &timestamp_;
				size = sizeof(TIMESTAMP_STRUCT);
				break;
			}
		default:
			return false;
		}

		if (size > buffer_size)
		{
			return false;
		}

		memcpy(buffer, data, size);

		if (actual_size != NULL)
		{
			*actual_size = size;
		}
	}

	return true;
}